﻿
Partial Public Class fceslite4(Of THREADSAFE As _boolean)
    Partial Private Class cluster4
        Private Const INVALID As SByte = npos
        Private Const DISK_CLUSTER_SIZE As UInt16 = 512
        Private Shared ReadOnly SIZEOF_INT64 As Byte = 0
        Private Shared ReadOnly CLUSTERID_OFFSET As Byte = 0
        Private Shared ReadOnly USED_OFFSET As Byte = 0
        Private Shared ReadOnly LENGTH_OFFSET As Byte = 0
        Private Shared ReadOnly NEXTCLUSTERID_OFFSET As Byte = 0
        Private Shared ReadOnly PREVCLUSTERID_OFFSET As Byte = 0
        Private Shared ReadOnly CHECKSUM As bytes = Nothing
        Private Shared ReadOnly CHECKSUM_SIZE As Byte = 0
        Private Shared ReadOnly CHECKSUM_OFFSET As Byte = 0
        Private Shared ReadOnly DATA_OFFSET As Byte = 0
        Private Shared ReadOnly MIN_CLUSTER_LENGTH As UInt16 = 0

        Shared Sub New()
            SIZEOF_INT64 = Global.sizeof(Of Int64)()
            CLUSTERID_OFFSET = 0
            USED_OFFSET = CLUSTERID_OFFSET + SIZEOF_INT64
            LENGTH_OFFSET = USED_OFFSET + SIZEOF_INT64
            NEXTCLUSTERID_OFFSET = LENGTH_OFFSET + SIZEOF_INT64
            PREVCLUSTERID_OFFSET = NEXTCLUSTERID_OFFSET + SIZEOF_INT64
            CHECKSUM = create_checksum()
            CHECKSUM_OFFSET = PREVCLUSTERID_OFFSET + SIZEOF_INT64
            CHECKSUM_SIZE = CHECKSUM.size()
            DATA_OFFSET = CHECKSUM_SIZE + CHECKSUM_OFFSET
            MIN_CLUSTER_LENGTH = assert_return(DISK_CLUSTER_SIZE - DATA_OFFSET > 0, DISK_CLUSTER_SIZE - DATA_OFFSET)
        End Sub

        Private Shared Function create_checksum() As bytes
            Dim s As String = Nothing
            Dim i As Int64 = 0
            For i = 0 To (Global.sizeof(Of Int64)() << bitshiftInByte >> 1) - 1
                s += "01"
            Next
            assert(strlen(s) = (Global.sizeof(Of Int64)() << bitshiftInByte))
            assert(convertor.toint64(s, i, 2))
            Return assert_return(Not convertor.tobytes(i) Is Nothing, convertor.tobytes(i))
        End Function

        Private ReadOnly _clusterId As Int64 = INVALID
        Private ReadOnly _diskoffset As Int64 = INVALID
        Private ReadOnly _length As Int64 = INVALID
        Private ReadOnly _vd As virtdisk = Nothing
        Private _used As Int64 = INVALID
        Private _prevClusterId As Int64 = INVALID
        Private _nextClusterId As Int64 = INVALID

        Public Function clusterId() As Int64
            Return _clusterId
        End Function

        Public Function diskoffset() As Int64
            Return _diskoffset
        End Function

        Public Function length() As Int64
            Return _length
        End Function

        Public Function empty() As Boolean
            Return used() = 0
        End Function

        Public Function used() As Int64
            If free() Then
                Return 0
            Else
                Return used_raw()
            End If
        End Function

        Public Function remain() As Int64
            Return length() - used()
        End Function

        Public Function full() As Int64
            Return remain() = 0
        End Function

        Private Shared Function end_diskoffset(ByVal diskoffset As Int64, ByVal length As Int64) As Int64
            Return diskoffset + DATA_OFFSET + length
        End Function

        Public Function end_diskoffset() As Int64
            Return end_diskoffset(diskoffset(), length())
        End Function

        Public Function vd() As virtdisk
            assert(valid_virtdisk(_vd))
            Return _vd
        End Function

        Public Function free() As Boolean
            If used_raw() = INVALID Then
                assert(nextClusterId() = INVALID AndAlso prevClusterId() = INVALID)
                Return True
            Else
                Return False
            End If
        End Function

        Private Property used_raw() As Int64
            Get
                Return _used
            End Get
            Set(ByVal value As Int64)
                assert(valid_used(value, length()))
                If _used <> value Then
                    _used = value
                    write_used()
                End If
            End Set
        End Property

        Public Property prevClusterId() As Int64
            Get
                Return _prevClusterId
            End Get
            Set(ByVal value As Int64)
                assert(valid_chainClusterId(value))
                If _prevClusterId <> value Then
                    _prevClusterId = value
                    write_prevClusterId()
                End If
            End Set
        End Property

        Public Function has_prevCluster() As Boolean
            Return valid_clusterId(prevClusterId())
        End Function

        Public Sub clear_prevClusterId()
            prevClusterId() = INVALID
        End Sub

        Public Property nextClusterId() As Int64
            Get
                Return _nextClusterId
            End Get
            Set(ByVal value As Int64)
                assert(valid_chainClusterId(value))
                If _nextClusterId <> value Then
                    _nextClusterId = value
                    write_nextClusterId()
                End If
            End Set
        End Property

        Public Function has_nextCluster() As Boolean
            Return valid_clusterId(nextClusterId())
        End Function

        Public Sub clear_nextClusterId()
            nextClusterId() = INVALID
        End Sub

        Private Shared Function valid_clusterId(ByVal id As Int64) As Boolean
            Return id >= 0
        End Function

        Private Shared Function valid_chainClusterId(ByVal id As Int64) As Boolean
            Return valid_clusterId(id) OrElse id = INVALID
        End Function

        Private Shared Function valid_diskoffset(ByVal diskoffset As Int64) As Boolean
            Return diskoffset >= 0
        End Function

        Private Shared Function valid_length(ByVal length As Int64) As Boolean
            Return length > 0
        End Function

        Private Shared Function valid_virtdisk(ByVal vd As virtdisk) As Boolean
            Return Not vd Is Nothing AndAlso vd.valid()
        End Function

        Private Shared Function valid_used(ByVal used As Int64, ByVal length As Int64) As Boolean
            Return used = INVALID OrElse
                   (used >= 0 AndAlso used <= length)
        End Function

        Private Shared Function valid_used_chainClusterId_combination(ByVal used As Int64,
                                                                      ByVal prevClusterId As Int64,
                                                                      ByVal nextClusterId As Int64) As Boolean
            Return used <> INVALID OrElse (prevClusterId = INVALID AndAlso nextClusterId = INVALID)
        End Function

        Public Sub New(ByVal clusterId As Int64,
                       ByVal diskoffset As Int64,
                       ByVal length As Int64,
                       ByVal vd As virtdisk)
            Me.New(clusterId, diskoffset, INVALID, length, INVALID, INVALID, vd)
            write_cluster_structure()
        End Sub

        Private Sub New(ByVal clusterId As Int64,
                        ByVal diskoffset As Int64,
                        ByVal used As Int64,
                        ByVal length As Int64,
                        ByVal prevClusterId As Int64,
                        ByVal nextClusterId As Int64,
                        ByVal vd As virtdisk)
            assert(valid_clusterId(clusterId))
            assert(valid_diskoffset(diskoffset))
            assert(valid_used(used, length))
            assert(valid_length(length))
            assert(valid_chainClusterId(prevClusterId))
            assert(valid_chainClusterId(nextClusterId))
            assert(valid_used_chainClusterId_combination(used, prevClusterId, nextClusterId))

            _clusterId = clusterId
            _diskoffset = diskoffset
            _used = used
            _length = decide_length(length)
            _prevClusterId = prevClusterId
            _nextClusterId = nextClusterId
            _vd = vd
        End Sub

        Private Shared Function decide_length(ByVal i As Int64) As Int64
            Dim rtn As Int64 = 0
            If i <= MIN_CLUSTER_LENGTH Then
                rtn = MIN_CLUSTER_LENGTH
            ElseIf (i - MIN_CLUSTER_LENGTH) Mod DISK_CLUSTER_SIZE = 0 Then
                rtn = i
            Else
                rtn = (((i - MIN_CLUSTER_LENGTH) Mod DISK_CLUSTER_SIZE) + 1) * DISK_CLUSTER_SIZE + MIN_CLUSTER_LENGTH
            End If
            assert(((rtn + DATA_OFFSET) Mod DISK_CLUSTER_SIZE) = 0)
            Return rtn
        End Function

        Public Shared Function ctor(ByVal vd As virtdisk,
                                    ByVal diskoffset As Int64,
                                    ByVal expected_clusterId As Int64,
                                    ByRef rtn As cluster4) As Boolean
            If vd Is Nothing OrElse diskoffset < 0 Then
                Return False
            Else
                Dim clusterId As Int64 = INVALID
                Dim used As Int64 = INVALID
                Dim length As Int64 = INVALID
                Dim prevClusterId As Int64 = INVALID
                Dim nextClusterId As Int64 = INVALID
                If read_cluster_structure(vd,
                                          diskoffset,
                                          clusterId,
                                          used,
                                          length,
                                          prevClusterId,
                                          nextClusterId) AndAlso
                   clusterId = expected_clusterId Then
                    rtn = New cluster4(clusterId, diskoffset, used, length, prevClusterId, nextClusterId, vd)
                    Return True
                Else
                    Return False
                End If
            End If
        End Function
    End Class
End Class
